package bsh;

import bsh.exception.CompilationFailedException;
import bsh.meta.JavaClassWrapper;
import bsh.meta.Scope;
import bsh.meta.TypeWrapper;
import bsh.meta.UnfinishedMethodDescriptor;

/**
 * Olha o {@link BSHMethodDeclaration}, parseando informacoes sobre o metodo
 * 
 * @author takeshi
 */
public class MethodDeclarationParser {

    private final BSHMethodDeclaration node;

    private final CompilationUnit unit;

    private TypeWrapper returnType;

    private Parameter[] params;

    private BSHBlock methodBlock;

    private final UnfinishedMethodDescriptor methodDescriptor;

    private final Scope declaringScope;

    /**
     * @param node o node
     * @param unit o unit
     * @param declaringScope the declared {@link Scope}
     * @throws CompilationFailedException e
     */
    public MethodDeclarationParser(BSHMethodDeclaration node, CompilationUnit unit, Scope declaringScope) throws CompilationFailedException {
        super();
        this.node = node;
        this.node.insureNodesParsed();
        this.unit = unit;
        this.declaringScope = declaringScope;
        this.methodDescriptor = this.toTypeDescriptor();
    }

    /**
     * @return {@link UnfinishedMethodDescriptor}
     * @throws CompilationFailedException e
     */
    private UnfinishedMethodDescriptor toTypeDescriptor() throws CompilationFailedException {
        SimpleNode returnOrParams = this.node.getChild(0);
        this.returnType = JavaClassWrapper.OBJECT_WRAPPER;
        if (returnOrParams instanceof BSHReturnType) {
            this.returnType = this.parseReturnType((BSHReturnType) returnOrParams);
            this.params = this.parseParameters((BSHFormalParameters) this.node.getChild(1));
            if (this.node.jjtGetNumChildren() > 2) {
                this.methodBlock = (BSHBlock) this.node.getChild(2 + this.node.numThrows);
            } else {
                this.methodBlock = null;
            }
        } else {
            this.params = this.parseParameters((BSHFormalParameters) this.node.getChild(0));
            this.methodBlock = (BSHBlock) this.node.getChild(1 + this.node.numThrows);
        }
        String methodName = this.node.name;
        return new UnfinishedMethodDescriptor(new TypeWrapper[0], this.unit.thisType(), methodName, toTypeArray(this.params),
                this.returnType);
    }

    /**
     * Coleta os parametros
     * 
     * @param parameters Parameter
     * @return TypeWrapper
     */
    private static TypeWrapper[] toTypeArray(Parameter[] parameters) {
        TypeWrapper[] array = new TypeWrapper[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            array[i] = parameters[i].getType();
        }
        return array;
    }

    /**
     * @param child parameter node
     * @return Parameter
     * @throws CompilationFailedException e
     */
    private Parameter[] parseParameters(BSHFormalParameters child) throws CompilationFailedException {
        return new FormalParameterParser().parseParameters(child, this.declaringScope);
    }

    /**
     * @param returnOrParams {@link BSHReturnType}
     * @return {@link TypeWrapper}
     * @throws CompilationFailedException e
     */
    private TypeWrapper parseReturnType(BSHReturnType returnOrParams) throws CompilationFailedException {
        if (returnOrParams.isVoid) {
            return JavaClassWrapper.VOID_WRAPPER;
        }
        return FormalParameterParser.extractType((BSHType) returnOrParams.getChild(0), this.declaringScope);
    }

    /**
     * @return {@link UnfinishedMethodDescriptor}
     */
    public UnfinishedMethodDescriptor getMethodDescriptor() {
        return this.methodDescriptor;
    }

    /**
     * @return {@link TypeWrapper}
     */
    public TypeWrapper getReturnType() {
        return this.returnType;
    }

    /**
     * @return Parameter
     */
    public Parameter[] getParams() {
        return this.params;
    }

    /**
     * @return BSHBlock
     */
    public BSHBlock getMethodBlock() {
        return this.methodBlock;
    }

    /**
     * @return boolean
     */
    public boolean isConstructor() {
        return this.node.returnTypeNode == null && this.node.name.equals(this.unit.thisType().simpleName());
    }

    /**
     * @return int
     */
    public int getLineNumber() {
        return node.getLineNumber();
    }

}
